package com.haredot.filter;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.haredot.entity.UserPrincipal;
import com.haredot.holder.TokenContextHolder;
import com.haredot.properties.JwtProperties;
import com.haredot.utils.JWTGzipCompression;
import com.haredot.utils.JwtUtils;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.rememberme.InvalidCookieException;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;

/**
 * 按照 JWT 授权标准，从请求头上 获取 Authorization 头，并获取 访问令牌
 * 根据 访问令牌 ，获取 用户身份信息
 */
public class BearerTokenAuthorizationFilter extends OncePerRequestFilter {

    private static final String AUTHORIZATION_PREFIX = "Bearer " ;

    private JwtProperties jwtProperties;

    public BearerTokenAuthorizationFilter(JwtProperties jwtProperties) {
        this.jwtProperties = jwtProperties;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取 请求头信息 Authorization
        String authorization = request.getHeader("Authorization");
        // 如果没有该头信息，或者该头信息 不是以指定的格式开头，则继续向下执行
        if (!StringUtils.hasLength(authorization) || !authorization.startsWith(AUTHORIZATION_PREFIX)) {
            filterChain.doFilter(request, response);
            return ;
        }
        // 获取 访问令牌
        String accessToken = authorization.substring(AUTHORIZATION_PREFIX.length()) ;

        try {
            // GZip 解压缩 令牌
            accessToken = JWTGzipCompression.decompressJWTToken(accessToken) ;
            // 验证令牌
            DecodedJWT decodedJWT = JwtUtils.validToken(this.jwtProperties, accessToken);
            // 获取 令牌中的身份信息
            String username = decodedJWT.getSubject();
            String jwtId = decodedJWT.getId();
            Instant issuedAtAsInstant = decodedJWT.getIssuedAtAsInstant();
            String uniqueId = Base64.getEncoder().encodeToString((username + ":" + issuedAtAsInstant.getEpochSecond()).getBytes(StandardCharsets.UTF_8));

            if (!Objects.equals(jwtId, uniqueId)) throw new InvalidCookieException("令牌校验失败");

            // 从令牌中获取 认证信息
            List<String> authoritiesList = decodedJWT.getClaim("authorities").asList(String.class);

            // 获取令牌中的 UID
            Long uid = decodedJWT.getClaim("uid").asLong();

            // 将 令牌信息放置到 当前线程中
            TokenContextHolder.setToken(accessToken, uid);

            // JSON 反序列化
            List<SimpleGrantedAuthority> permissionGrantedAuthorities = authoritiesList.stream().map(auth -> new SimpleGrantedAuthority(auth)).toList();

            UserPrincipal userPrincipal = new UserPrincipal(uid, username);
            // 构建一个 认证成功的对象
            Authentication authenticated = UsernamePasswordAuthenticationToken.authenticated(userPrincipal, null, permissionGrantedAuthorities);
            // 将 认证信息存储到 SpringSecurity 容器中
            SecurityContextHolder.getContext().setAuthentication(authenticated);
            // 继续详细执行
            filterChain.doFilter(request, response);

        }catch (AuthenticationException e) {
            response.setStatus(401);
            Map<String, Object> ret = new HashMap<>();
            ret.put("status", false);
            ret.put("message", e.getMessage());
            new MappingJackson2HttpMessageConverter().write(ret, MediaType.APPLICATION_JSON, new ServletServerHttpResponse(response));
        }catch (Exception e) {
            throw new InvalidCookieException(e.getMessage()) ;
        }finally {
            TokenContextHolder.remove();
        }
    }
}
